package net.midnighttd.engine.view.sprite;

import net.midnighttd.engine.view.sprite.SpriteList;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Author: Adam Scarr
 * Date: 24/04/2009
 * Time: 8:58:29 PM
 */
public abstract class Sprite implements Comparable {
    private String name;
    private int width, height;
    private double rotation = 0;
    private int x, y;
    private final int z;
    private Queue<Movement> movementQueue = new ConcurrentLinkedQueue<Movement>();
    private LinkedList<MovementStateListener> movementStateListeners = new LinkedList<MovementStateListener>();
    private Movement currentMovement = null;
    private SpriteList children = new SpriteList();

    protected Sprite(Sprite copy) {
        this.name = copy.name;
        this.width = copy.width;
        this.height = copy.height;
        this.x = copy.x;
        this.y = copy.y;
        this.z = copy.z;
        this.rotation = copy.rotation;
    }

    public Sprite(String name, int width, int height, int z) {
        this.name = name;
        this.width = width;
        this.height = height;
        this.z = z;
    }

    public void addChild(Sprite s) {
        children.add(s);
    }

    public abstract void draw(Graphics g);

    public void drawAll(Graphics g) {
        draw(g);
        if(!children.isEmpty()) {
            g.translate(x, y);
            children.drawAll(g);
            g.translate(-x, -y);
        }
    }

    public synchronized void addMovementStateListener(MovementStateListener m) {
        movementStateListeners.add(m);
    }

    private synchronized void notifyMovementEvent(MovementState state, Movement movement) {
        for(MovementStateListener listener: movementStateListeners) {
            listener.movementStateChanged(this, state, movement);
        }
    }

    public void update() {
        updateMovement();
    }

    public synchronized void updateMovement() {
        if(currentMovement == null) {
            currentMovement = movementQueue.poll();
            if(currentMovement == null) return;
            notifyMovementEvent(MovementState.begin, currentMovement);
            if(currentMovement == null) return;
            currentMovement.start(x, y, rotation);
        }

        Movement.Update update = currentMovement.getUpdate();

        x = update.x;
        y = update.y;

        rotation = update.rotation;
        if(update.complete) {
            notifyMovementEvent(MovementState.end, currentMovement);
            currentMovement = null;
        }
    }

     /**
     * Stops the sprite moving and removes all pending movements
     */
    public synchronized void stop() {
        movementQueue.clear();
        //currentMovement = null;
    }

    /**
     * Adds a destination to the movement queue.
     * @param x     X screen coordinate to move to
     * @param y     Y screen coodinate to move to
     * @param speed  The time allocated for this movement, in pixels per second
     */
    public void moveTo(int x, int y, double speed) {
        movementQueue.add(new Movement(x,y, speed, false));
    }

    /**
     * Moves by a given amount, after all prior movements have completed.
     * @param x     amount to move on horizontal
     * @param y     amount to move on vertical
     * @param speed  The time allocated for this movement, in pixels per second
     */
    public void moveBy(int x, int y, double speed) {
        movementQueue.add(new Movement(x,y, speed, true));
    }

    /**
     * Rotates to a given heading (0 degrees is left facing).
     * @param degrees   The final rotation in radians.
     * @param speed      The time allocated for this movement, in radians per second.
     */
    public void rotateTo(double degrees, double speed) {
        movementQueue.add(new Movement(degrees, speed, false));
    }

    /**
     * Rotates a given amount.
     * @param degrees   The amount to rotate by in radians.
     * @param speed      The time allocated for this movement, in radians per second.
     */
    public void rotateBy(double degrees, double speed) {
        movementQueue.add(new Movement(degrees, speed, true));
    }

    public void rotateToFace(Sprite sprite) {
        rotation = Math.atan2(sprite.y - y, sprite.x - x);
    }

    public String getName() {
        return name;
    }

    public void setLocation(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

     public int getCenterX() {
        return x + width / 2;
    }

    public int getCenterY() {
        return y + height / 2;
    }

     public int compareTo(Object o) {
        if(o instanceof Sprite) {
            return z - ((Sprite)o).z ;
        }
        return 0;
    }

    public Integer getZ() {
        return z;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public double getRotation() {
        return rotation;
    }

    public abstract Sprite copy();

    public Image toImage() {
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
        drawAll(img.getGraphics());

        return img;
    }
}
